package org.paic.vertx.httpwrapper

import org.vertx.groovy.core.buffer.Buffer
import org.vertx.groovy.core.http.impl.DefaultHttpServerRequest
import org.vertx.groovy.core.http.impl.DefaultHttpServerResponse

import org.paic.vertx.httpwrapper.render.Render
import org.paic.vertx.httpwrapper.render.Smarty4jRender
import org.paic.vertx.httpwrapper.session.SessionOpter
import org.paic.vertx.httpwrapper.session.SessionStore
import org.paic.vertx.httpwrapper.session.JedisSessionStore

import io.netty.handler.codec.http.Cookie
import io.netty.handler.codec.http.CookieDecoder
import io.netty.handler.codec.http.DefaultCookie
import io.netty.handler.codec.http.ServerCookieEncoder

import com.alibaba.fastjson.JSON

class MetaClassExtender {
	// singleton
	private MetaClassExtender(){}
	private static MetaClassExtender inst = new MetaClassExtender()
	public static MetaClassExtender getInst(){
		inst
	}

	public void config(Properties conf){
		this.conf = conf
		init()
	}

	Properties conf

	String charset

	Render render = new Smarty4jRender()
	// session store
	SessionOpter sessionOpter

	private boolean isExtendAlready = false
	private void init() {
		sessionOpter = 'local' == conf.sessionType ?
				SessionStore.getInstance() :
				JedisSessionStore.getInstance(conf.redisHost)
		sessionOpter.setIntervalExpired(Long.valueOf(conf.sessionExpiredInterval))

		// set metaclass properties once only
		if(!isExtendAlready){
			extendResp()
			extendReq()

			isExtendAlready = true
		}

		charset = conf.charset ?: 'utf-8'
	}

	// get session key in session store
	private String getSessionKey(DefaultHttpServerRequest req, boolean generateCookie = false){
		String cookieVal = req.getCookie(Constants.sessionCookieName)
		if(cookieVal == null){
			if(!generateCookie)
				return null

			cookieVal = UUID.randomUUID().toString()
			// add cookie
			req.response.addCookie([name: Constants.sessionCookieName, value: cookieVal, path: '/'])
		}

		String sessionKey = Constants.sessionStoreKeyPre + cookieVal
		sessionOpter.setVisit(sessionKey)
		return sessionKey
	}

	private void extendReq(){
		def metaClassReq = DefaultHttpServerRequest.class.metaClass
		// get cookies, use thread local better
		metaClassReq.getCookies = {
			// check if decode already
			final String localKey = '_cookies'
			Object r = delegate.getLocal(localKey)
			if(r != null)
				return r

			String header = delegate.headers.get('Cookie')
			if(header == null)
				return Collections.emptySet()

			def cookies = CookieDecoder.decode(header)
			delegate.setLocal(localKey, cookies)
			return cookies
		}
		metaClassReq.getCookie = {String name ->
			Set<Cookie> cookies = delegate.getCookies()
			// for perform better than closure ?
			for(one in cookies){
				if(name == one.name)
					return one.value
			}
			return null
		}

		metaClassReq.session = {String key, Object val = '' ->
			String sessionStoreKey = getSessionKey(delegate, val != '')
			if(sessionStoreKey == null)
				return null

			if(val == ''){
				String sessionJsonVal = sessionOpter.get(sessionStoreKey, key)
				return sessionJsonVal == null ? null : JSON.parse(sessionJsonVal)
			}else{
				sessionOpter.set(sessionStoreKey, key, JSON.toJSONString(val))
			}
		}
		metaClassReq.delSession = {String key ->
			String sessionStoreKey = getSessionKey(delegate)
			if(sessionStoreKey)
				sessionOpter.del(sessionStoreKey, key)
		}
		metaClassReq.clearSession = {
			String sessionStoreKey = getSessionKey(delegate)

			if(sessionStoreKey){
				sessionOpter.clear(sessionStoreKey)
				delegate.response.addCookie([name: Constants.sessionCookieName, value: '', path: '/', maxAge: 0])
			}
		}

		// application/json Content-Type handle
		metaClassReq.jsonHandler = {Closure closure ->
			delegate.bodyHandler{body ->
				try{
					def obj = JSON.parse(body.bytes)
					closure(obj)
				}catch(ex){
					delegate.response.with{
						statusCode = 500
						statusMessage = ex.message
						end()
					}
				}
			}
		}
		// bytes[] + params -> synchronized process
		metaClassReq.uploadSyncHandler = {Closure closure ->
			Buffer buffer = null

			delegate.expectMultiPart = true
			delegate.endHandler{
				def params = new HashMap()
				def attrs = delegate.formAttributes
				for(attr in attrs){
					params[attr.key] = attr.value
				}

				try{
					closure(buffer, params)
				}catch(ex){
					delegate.response.with{
						statusCode = 500
						statusMessage = ex.message
						end()
					}
				}
			}

			delegate.uploadHandler{upload ->
				upload.dataHandler{bb -> buffer = bb }
			}
		}
	}

	private void extendResp(){
		def metaClass = DefaultHttpServerResponse.class.metaClass
		// set cookie
		metaClass.addCookie = {Map item ->
			Cookie cookie = new DefaultCookie(item.name, item.value)
			for(key in Constants.cookieProperties){
				if(item[key] != null)
					cookie[key] = item[key]
			}

			delegate.headers.add('Set-Cookie', ServerCookieEncoder.encode(cookie))
		}
		metaClass.addCookieValue = {String name, String value ->
			Cookie cookie = new DefaultCookie(name, value)
			delegate.headers.add('Set-Cookie', ServerCookieEncoder.encode(cookie))
		}
		metaClass.delCookie = {String name ->
			Cookie cookie = new DefaultCookie(name, '')
			delegate.headers.add('Set-Cookie', ServerCookieEncoder.encode(cookie))
		}
		// output html using smarty4j
		metaClass.render = {String tplFile, Map map ->
			String contentTypeHeader = 'Content-type'
			if(delegate.headers.get(contentTypeHeader) == null)
				delegate.headers.set(contentTypeHeader, 'text/html;charset=' + charset)

			// merge model data
			def out = new ByteArrayOutputStream()
			render.render(conf.webroot + tplFile, map, out)
			delegate.end(new Buffer(out.toByteArray()))
		}
		// output json
		metaClass.json = {Object obj ->
			delegate.headers.set('Content-type', 'application/json;charset=' + charset)
			delegate.end(JSON.toJSONString(obj))
		}
		// download file
		// eg resp.download('my.word', bytes, ['Content-Type': 'ms/word'])
		metaClass.download = {String file, byte[] bytes, Map headers = null ->
			if(!headers)
				headers = ['Content-Type': 'application/octet-stream']
			headers.put('Content-Disposition', "attachment;filename=\"${file}\"")

			for(one in headers){
				delegate.headers.set(one.key, one.value)
			}
			delegate.end(new Buffer(bytes))
		}
		// redirect
		metaClass.redirect = {String location ->
			delegate.headers.set('Location', location)
			delegate.statusCode = 302
			delegate.end()
		}
	}

}